 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GenericGA.generationmaker.mutation;
using GenericGA.extrandom;
using GenericGA.generationmaker.selection;
using GenericGA.generationmaker.crossover;

namespace GenericGA.generationmaker
{
    /// <summary>
    /// A class for generating populations traditionally by selecting two chromosomes, crossing them over, mutating the offspring and adding the two offspring into the new generation. At the end, the next generation will consist of new offspring.
    /// </summary>
    /// <typeparam name="P">The phenotype type, or type of the candidate solutions.</typeparam>
    public class TraditionalEvolution<P> : GenerationMaker<P>
    {

        /// <summary>
        /// A random number generator.
        /// </summary>
        private readonly ExtRandom randGen;

        /// <summary>
        /// The size of the population in the next generation.
        /// </summary>
        public int PopulationSize {
            get
            {
                return populationSize;
            }
            set
            {
                if (value < 1)
                {
                    throw new ArgumentException();
                }
                populationSize = value;
            }
        }
        private int populationSize;

        /// <summary>
        /// The mutation strategy to use for generating the next generation.
        /// </summary>
        public Mutation Mutation { get; set; }

        /// <summary>
        /// The crossover strategy to use for generating the next generation.
        /// </summary>
        public Crossover Crossover { get; set; }

        /// <summary>
        /// The selection strategy to use for choosing individuals when generating the next generation.
        /// </summary>
        public Selection<P> Selection { get; set; }
        
        /// <summary>
        /// Create a traditional generation maker.
        /// </summary>
        /// <param name="randGen">A random number generator.</param>
        /// <param name="populationSize">The number of random individuals to generate per generation.</param>
        /// <param name="mutation">The mutation strategy to use for generating the next generation.</param>
        /// <param name="crossover">The crossover strategy to use for generating the next generation.</param>
        /// <param name="selection">The selection strategy to use for choosing individuals when generating the next generation.</param>
        public TraditionalEvolution(ExtRandom randGen, int populationSize, Mutation mutation, Crossover crossover, Selection<P> selection)
        {
            this.randGen = randGen;
            this.PopulationSize = populationSize;
            this.Mutation = mutation;
            this.Crossover = crossover;
            this.Selection = selection;
        }

        /// <summary>
        /// Create a traditional generation maker using default genetic operators.
        /// </summary>
        /// <param name="randGen">A random number generator.</param>
        /// <param name="populationSize">The number of random individuals to generate per generation.</param>
        public TraditionalEvolution(ExtRandom randGen, int populationSize)
            : this(randGen, populationSize, new SinglePointMutation(randGen, 0.001), new ProbabilisticCrossover(randGen, 0.9, new SinglePointCrossover(randGen)), new FitnessProportionateSelection<P>(randGen))
        {
        }

        /// <summary>
        /// Return the next generation.
        /// </summary>
        /// <param name="currGeneration">The current generation.</param>
        /// <param name="phenotypeBuildingFunction">A phenotype building function for the creation of individuals.</param>
        /// <param name="fitnessFunction">A fitness function for the creation of individuals.</param>
        /// <returns>The next generation.</returns>
        public override List<List<ulong>> NextGeneration(List<Individual<P>> currGeneration, GA<P>.PhenotypeBuildingFunctionType phenotypeBuildingFunction, GA<P>.FitnessFunctionType fitnessFunction)
        {
            List<List<ulong>> newPopulation = new List<List<ulong>>();

            Selection.InitSelection(currGeneration, fitnessFunction);
            for (int i = 0; i < populationSize; i++)
            {
                List<ulong> offspring1 = Selection.Select().Chromosome;
                List<ulong> offspring2 = Selection.Select().Chromosome;
                
                Crossover.Cross(offspring1, offspring2);
                Mutation.Mutate(offspring1);
                Mutation.Mutate(offspring2);

                newPopulation.Add(offspring1);
                newPopulation.Add(offspring2);
            }
            
            return newPopulation;
        }

    }
}
